package shoozhoo.libandrotranslation;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import org.xmlpull.v1.XmlSerializer;

import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.util.Xml;

public class StringResources {
	public static final String USER_SAVE_DIR = Environment.getExternalStorageDirectory()+"/MyTranslation";

	private List<String> pkgList = new ArrayList<String>();

	// key:packageName
	private Map<String,List<StringRes>> resources = new TreeMap<String, List<StringRes>>();

	private Map<Integer, StringRes> id2res = new HashMap<Integer, StringRes>();
	private boolean ignoreSelfResource = true;
	private String dir = USER_SAVE_DIR;
	private List<String> ignorePatterns = new ArrayList<String>();

	public StringResources(String packageName){
		pkgList.add(packageName);
	}

	public StringResources(List<String> packageList){
		pkgList.addAll(packageList);
	}

	/*
	 * Translation file is saved this dir.
	 */
	public void setDir(String d){
		this.dir = d;
	}
	public void addIgnorePattern(String pattern){
		this.ignorePatterns.add(pattern);
	}
	public void setIgnorePatterns(List<String> patterns){
		if(patterns==null){
			this.ignorePatterns.clear();
		}else{
			this.ignorePatterns = patterns;
		}
	}

	/*
	 * True when specified resource name is ignore pattern.
	 */
	private boolean matchIgnorePattern(String resName){
		if(resName==null){
			return false;
		}
		for (String pattern : this.ignorePatterns) {
			if(resName.matches(pattern)){
				return true;
			}
		}
		return false;
	}


	/*
	 * Get user translation string from R.id .
	 */
//	public String getString(int resId){
//		if(id2res.isEmpty()){
//			return null;
//		}
//		StringRes res = id2res.get(resId);
//		if(res==null){
//			return null;
//		}
//		return res.getUserString();
//	}

	/*
	 * All clear user translation.
	 */
	public void clear(){
		for(String key : resources.keySet()){
			List<StringRes> list = resources.get(key);
			for (StringRes strRes : list) {
				strRes.setUserString("");
			}
		}
	}

	/*
	 * Update user translation.
	 */
	public void updateStringResource(StringRes res){
		if(res==null){
			return;
		}
		for(String key : resources.keySet()){
			List<StringRes> list = resources.get(key);
			for (StringRes strRes : list) {
				if(strRes.getResourceName().equals(res.getResourceName())){
					strRes.setUserString(res.getUserString());
					return;
				}
			}
		}
	}

	/*
	 * Load R.string and user translation file.
	 */
	public void loadStringResources(Context ctx) throws LibAndTransException{
		LibAndTransUtil.log("Pkg "+this.pkgList.size());
		for (String pkg : this.pkgList) {
			List<StringRes> rstrList = this.readRString(ctx, pkg);

			Map<String, String> userResMap = readUserResource(ctx, pkg);
			for (StringRes res : rstrList) {
				String translation = userResMap.get(res.getResourceName());
				if(translation!=null){
					res.setUserString(translation);
					id2res.put(res.getResId(), res);
				}
			}
			resources.put(pkg, rstrList);
		}
	}

	/*
	 * Get R.string and user translation.
	 */
	public List<StringRes> getStringResources(){
		List<StringRes> rval = new ArrayList<StringRes>();
		for (List<StringRes> list : resources.values()) {
			rval.addAll(list);
		}
		return rval;
	}


	/*
	 * Get R.string by reflection.
	 */
	private List<StringRes> readRString(Context ctx, String pkg) throws LibAndTransException{
		LibAndTransUtil.log("readRString "+pkg);
		List<StringRes> rval = new ArrayList<StringRes>();
		String fName = null;
		try{
			Class<?> cls = Class.forName(pkg+".R$string");
			Field[] flds = cls.getFields();
			for (Field f : flds) {
				try{
					int id = f.getInt(null);
					fName = f.getName();
					StringRes res = new StringRes(fName,id, ctx.getString(id), "");
					if(matchIgnorePattern(res.getResourceName())
							||(this.ignoreSelfResource
									&& res.getResourceName().startsWith("zzlibandrotranslation_"))){
						continue;
					}
					rval.add(res);
				}catch (Exception e) {
					LibAndTransUtil.log("##### R.string.("+fName+") reflection error.",e);
				}
			}
		}catch (Exception e) {
			throw new LibAndTransException("readRString Error.",e);
		}
		return rval;

	}

	/*
	 * Read user translations from property file.
	 */
	private Map<String, String> readUserResource(Context ctx, String pkg) throws LibAndTransException{
		HashMap<String, String> rval = new HashMap<String, String>();
		File f = userResourceFile(ctx, pkg, this.dir);
		if(!f.exists()){
			return rval;
		}
		Properties prop = new Properties();

		FileInputStream fis = null;
		BufferedInputStream bis = null;
		try{
			fis = new FileInputStream(f);
			bis = new BufferedInputStream(fis);
			prop.load(bis);

			for(Object key : prop.keySet()){
				String skey = (String)key;
				rval.put(skey, prop.getProperty(skey, ""));
			}
			return rval;
		}catch (Exception e) {
			LibAndTransUtil.log(e.getMessage());
			throw new LibAndTransException("", e);
		}finally{
			LibAndTransUtil.safeclose(bis);
			LibAndTransUtil.safeclose(fis);
		}
	}

	/*
	 * Save user translations to property file.
	 */
	public void saveUserResource(Context ctx) throws LibAndTransException{
		for (String pkg : this.pkgList) {
			saveUserResource(ctx, pkg);
		}
	}
	private void saveUserResource(Context ctx, String pkg) throws LibAndTransException{
		List<StringRes> list = this.resources.get(pkg);
		if(list==null){
			return;
		}
		File d = new File(this.dir);
		d.mkdirs();
		File f = userResourceFile(ctx, pkg, this.dir);
		Properties prop = new Properties();
		for (StringRes res : list) {
			if(res.getUserString()==null || res.getUserString().matches("\\s*")){
				continue;
			}
			prop.setProperty(res.getResourceName(), res.getUserString());
		}
		FileOutputStream fos = null;
		try{
			fos = new FileOutputStream(f);
			prop.save(fos, "AppTrans");
		}catch (Exception e) {
			LibAndTransUtil.log(e.getMessage());
			throw new LibAndTransException("", e);
		}finally{
			LibAndTransUtil.safeclose(fos);
		}

	}

	public Uri getTranslationXmlFile(Context ctx, String pkg){
		return save2xml(ctx, pkg);
	}

	/*
	 * Save to XML file, and return its Uri.
	 */
	private Uri save2xml(Context ctx, String pkg){
		List<StringRes> list = this.resources.get(pkg);
		if(list==null){
			return null;
		}
		File d = new File(this.dir);
		d.mkdirs();

		XmlSerializer serializer = Xml.newSerializer();
		FileWriter fw = null;
		try {
			File file = xmlFile(ctx, pkg, this.dir);
			fw = new FileWriter(file);
			try{
				serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
			}catch (Exception e) {}
			serializer.setOutput(fw);
			serializer.startDocument("UTF-8", true);
			serializer.startTag("", "resources");
			for (StringRes res : list) {
				if(res.getUserString()==null || res.getUserString().matches("\\s*")){
					continue;
				}
				serializer.startTag("", "string");
				serializer.attribute("", "name", res.getResourceName());
				String text = res.getUserString().replaceAll("\"", "\\\\\"");
				serializer.text("\""+text+"\"");
				serializer.endTag("", "string");
			}
			serializer.endTag("", "resources");
			serializer.endDocument();
			return Uri.fromFile(file);
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally{
			LibAndTransUtil.safeclose(fw);
		}

	}
	private static File xmlFile(Context ctx, String pkg, String dir){
		return new File(dir, "string_"+ctx.getPackageName()+".xml");
	}
	private static File userResourceFile(Context ctx, String pkg, String dir){
		return new File(dir, ctx.getPackageName()+"_"+pkg+".properties");
	}

	public boolean isIgnoreSelfResource() {
		return ignoreSelfResource;
	}

	/*
	 * If you want to translation LibAndroTranslation's resource, set true.
	 */
	public void setIgnoreSelfResource(boolean ignoreSelfResource) {
		this.ignoreSelfResource = ignoreSelfResource;
	}
}
